/*
 * Copyright 2011 CraneLeeon authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.crane.webtemplate;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

/**
 *
 * @author CraneLeeon
 */
public class ObjectContext {
    private static final Log logger = LogFactory.getLog(ObjectContext.class);
    private static String DefaultConfigFilePath="web-template.xml";
    private String configFilePath;
    protected Map<String,Object> objMap =  new HashMap();
    public Object getObject(String idOrFullname){
        Element elem = (Element) objMap.get(idOrFullname);
        if(elem == null) return null;
        Object[] reobj = null;
        try {
            reobj = createObjInstance(elem,false);
        } catch (ClassNotFoundException ex) {
            
        } catch (ClassCastException ex) {
            
        } catch (InstantiationException ex) {
            
        } catch (IllegalAccessException ex) {
            
        }
        return (reobj!=null? reobj[0]:null);
    }
    
    private static ObjectContext ctx;
    public static ObjectContext getInstance(){
        if(ctx==null) ctx = new ObjectContext();
        return  ctx;
    }

    public ObjectContext() {
        init();
    }
    
    public ObjectContext(String configFilePath) {
        this.configFilePath = configFilePath;
        init();
    }
    
    protected void init(){
        try {
            SAXReader reader = new SAXReader();
            if(configFilePath == null || configFilePath.isEmpty()) {
                configFilePath = DefaultConfigFilePath;
            }
            Document doc = reader.read(this.getClass().getClassLoader().getResource(configFilePath));
            Element cfg = doc.getRootElement();
            List<Element> includes = cfg.elements("includes");
            for(Element include : includes){
                String path = include.attributeValue("resource").trim();
                for(Element el : (List<Element>) reader.read(this.getClass().getClassLoader().getResource(path)).getRootElement().elements()){
                    cfg.add(el.createCopy());
                }
            }
            //Initializations
            //static
            List<Element> sfs = cfg.elements("init-static-field");
            for(Element sf : sfs){
                String classname = sf.attributeValue("class").trim();
                Element fields_el = sf.element("field-list");
                parseFieldsInInitParam(null, classname, fields_el);
            }
            //object
            List<Element> objs = cfg.elements("init-object");
            for(Element obj : objs){
//                String classname = obj.attributeValue("class").trim();
//                String classid = null;
//                try {
//                    classid = obj.attributeValue("id").trim();
//                } catch (Exception e) {
//                }
//                Element fields_el = obj.element("field-list");
//                Object ins = Class.forName(classname).newInstance();
//                parseFieldsInInitParam(ins, classname, fields_el);
                Object[] reobj = createObjInstance(obj, true);
                this.objMap.put((String) reobj[1], obj);
                if (reobj[2] != null) {
                    this.objMap.put((String) reobj[2], obj);
                }
//                this.objMap.put(classname, ins);
//                if(classid != null) this.objMap.put(classid, ins);
            }
            
        } catch (ClassNotFoundException classNotFoundException) {
            logger.debug(classNotFoundException);
        } catch (ClassCastException ex) {
            logger.debug(ex);
        } catch (InstantiationException instantiationException) {
            logger.debug(instantiationException);
        } catch (IllegalAccessException illegalAccessException) {
            logger.debug(illegalAccessException);
        }catch (Exception ex) {
            logger.debug(ex);
        }
    }
    
    /***
     * 
     * @param obj
     * @return Object array - [0]expected Object instance
     *                        [1]class name
     *                        [2]class id(alias)
     * @throws ClassNotFoundException
     * @throws InstantiationException
     * @throws IllegalAccessException 
     */
    private Object[] createObjInstance(Element obj, boolean initTime) throws ClassNotFoundException, InstantiationException, IllegalAccessException,ClassCastException{
        String classname = obj.attributeValue("class");
        if(classname == null) throw new ClassNotFoundException("No class definition!");
        String implclassname = obj.attributeValue("implements");
        if(implclassname!=null) {
            if(initTime 
                && !Class.forName(classname).isAssignableFrom(Class.forName(implclassname))){
                throw new ClassCastException("Implements error - " + implclassname + " can't be cast to " + classname);
            }
            classname = implclassname.trim();
        }else{
            classname = classname.trim();
        }
        Object ins = Class.forName(classname).newInstance();
        String classid = null;
        try {
            classid = obj.attributeValue("id").trim();
        } catch (Exception e) {
        }
        Element fields_el = obj.element("field-list");
        parseFieldsInInitParam(ins, classname, fields_el);
        return new Object[]{ins, obj.attributeValue("class"),classid};
    }
    
    public Field getFieldIteratively(String fieldname, Class currentclass){
        if(currentclass==null) return null;
        Field field = null;
        try {
            field = currentclass.getField(fieldname);
        } catch (Exception ex) {
            try {
                field = currentclass.getDeclaredField(fieldname);
            } catch (Exception e) {
                field = getFieldIteratively(fieldname, currentclass.getSuperclass());
            }
        } 
        if(field!=null)field.setAccessible(true);
        return field;
    }
    
    /***
     * parse fields iteratively
     * @param instance
     * @param classname
     * @param fields_el 
     */
    public void parseFieldsInInitParam(Object instance,String classname, Element fields_el){
        if(fields_el != null && !fields_el.isTextOnly()){
            try {
                Class cls = Class.forName(classname);
                for (Element field_el : (List<Element>) fields_el.elements("field")) {
                    String fname = field_el.attributeValue("name");
                    String fclsname = field_el.attributeValue("class");
                    String val = field_el.getTextTrim();
                    Field field = null;
                    field = getFieldIteratively(fname, cls);
                    Element ffields_el = field_el.element("field-list");
                    if(ffields_el!=null && !ffields_el.isTextOnly() && fclsname != null){
                        if(Modifier.isStatic(field.getModifiers())){
                            parseFieldsInInitParam(field.get(null), fclsname, ffields_el);
                        }else{
                            if(instance == null) throw new NullPointerException("Host Obejct for "+fclsname+" is null");
                            if(field == null) throw new NoSuchFieldException("Field "+fclsname+" can not be found");
                                Object _subobj= this.objMap.get(fclsname);
                                if(_subobj != null) {
                                    field.set(instance, _subobj);
                                }else{
                                    field.set(instance, Class.forName(fclsname).newInstance());
                                }
                                
                            parseFieldsInInitParam(field.get(instance), fclsname, ffields_el);
                        }
                        
                    } else if (field.isEnumConstant()) {
                        try {
                            Class choicetype = field.getClass();
                                Enum val_obj = Enum.valueOf(choicetype, val);
                                if(Modifier.isStatic(field.getModifiers()) || instance!=null){
                                    field.set(instance, val_obj);
                                }else{
                                    
                                }
                        } catch (Exception ex) {
                            logger.debug("Initial Parameter Error! ", ex);
                        }
                    } else {//set static properties in refered classes
                        try {
                            if (val.endsWith(".class")) {
                                field.set((instance!=null ? instance:null), Class.forName(val.replaceFirst(".class", "")));
                            } else {
                                Class fieldtype = null;
                                try {
                                    fieldtype = Class.forName(fclsname);
                                } catch (ClassNotFoundException classException) {
                                    try {
                                        fieldtype = Class.forName("java.lang." + fclsname);
                                    } catch (ClassNotFoundException classNotFoundException) {
                                        fieldtype = field.getType();
                                    }
                                }
                                Object val_obj = val;
                                Class converttype = String.class;
                                if(field.getType().equals(String.class)){
                                    converttype = Object.class;
                                }else if(field.getType().equals(Character.class)){
                                    converttype = char.class;
                                    val_obj = (char) Integer.valueOf(
                                            (val.startsWith("\\u"))? 
                                            val.substring(2):val, 16).intValue();
                                }
                                field.set((instance!=null ? instance:null), fieldtype.getMethod("valueOf",
                                        (converttype)).invoke(null, val_obj));
                            }
                        } catch (Exception ex) {
                            logger.debug("Initial Parameter Error! ", ex);
                        }

                    }
                }
            } catch (NoSuchFieldException ex) {
                logger.debug(ex);
            } catch (InstantiationException ex) {
               logger.debug(ex);
            } catch (ClassNotFoundException classNotFoundException) {
                logger.debug(classNotFoundException);
            } catch (SecurityException securityException) {
                logger.debug(securityException);
            }catch (IllegalArgumentException ex) {
                logger.debug(ex);
            } catch (IllegalAccessException ex) {
                logger.debug(ex);
            }
        }
    }
}
